package util

import (
	"fmt"
	"regexp"
	"sort"
	"strings"
	"sync"

	"cvevulner/common"

	"github.com/astaxie/beego/logs"
)

const (
	KwOpenGaussScore = "openGauss评分:"
	KwMindSporeScore = "MindSpore评分:"
	KwLooKengScore   = "openLooKeng评分:"
)

const (
	//KwAnalysisDesc Keywords for impact analysis
	KwAnalysisDesc = "影响性分析说明:"
	//KwOpenEulerScore Keywords scored by openEuler
	KwOpenEulerScore = "openEuler评分:"
	//KwEffectVersion Keywords of the affected version
	KwEffectVersion = "受影响版本排查(受影响/不受影响):"
	//KwAbiVersion Keywords of the abi version
	KwAbiVersion = "修复是否涉及abi变化(是/否):"

	Openeuler   = 1
	OpenGauss   = 2
	MindSpore   = 3
	OpenLookeng = 4
)

var (
	//LD Vulnerability description label
	LD = "LD"
	//IAD Impact analysis description label
	IAD = "IAD"
	//PA Principle analysis tags
	PA = "PA"
	//OES openEuler score tags
	OES = "OES"
	//OEV openEuler vector tags
	OEV = "OEV"
	//IV Affected version label
	IV = "IV"
	//CPMM Circumvention measures
	CPMM = "CPMM"
	//IW Impacted package
	IW = "IW"
	//CommentKeys Keyword parsed by the new version of comments
	CommentKeys      = []string{KwAnalysisDesc, KwOpenEulerScore, KwEffectVersion}
	EulerCommentKeys = []string{KwAnalysisDesc, KwOpenEulerScore, KwEffectVersion, KwAbiVersion}
)

var (
	//CvsScoreV3 nveScoreType v3
	CvsScoreV3 = "v3"
	//CvsScoreV2 nveScoreType V2
	CvsScoreV2 = "v2"
	// Regular match CVE information
	RegexpCveNumVaule = regexp.MustCompile(`CVE-\d+-\d+`)
	//RegexpCveNumber cveNum extract regexp
	RegexpCveNumber     = regexp.MustCompile(`漏洞编号[:：](?s:(.*?))漏洞归属组件[:：]`)
	regexpCveNumberLink = regexp.MustCompile(`\[(.*?)\]\((.*?)\)`)
	//RegexpCveComponents components extract regexp
	RegexpCveComponents = regexp.MustCompile(`漏洞归属组件[:：](?s:(.*?))漏洞归属的?版本[:：]`)
	//RegexpCveVersion cveVersion extract regexp
	RegexpCveVersion = regexp.MustCompile(`漏洞归属的?版本[:：](?s:(.*?))CVSS [Vv][23].[0-9xX]分值[:：]`)
	//RegexpCveScore cveScore extract regexp
	RegexpCveScore = regexp.MustCompile(`CVSS [Vv][23].[0-9xX]分值[:：](?s:(.*?))漏洞[简描]述[:：]`)
	//RegexpCveBriefDesc brief description extract regexp
	RegexpCveBriefDesc = regexp.MustCompile(`漏洞[简描]述[:：](?s:(.*?))影响性分析说明[:：]`)
	//RegexpCveBriefDesc new tpl brief description extract regexp
	RegexpCveBriefDescNew = regexp.MustCompile(`漏洞[简描]述[:：](?s:(.*?))漏洞公开时间[:：]`)
	//RegexpCveInfluencesDesc influences description regexp
	RegexpCveInfluencesDesc = regexp.MustCompile(`影响性分析说明[:：](?s:(.*?))原理分析[:：]`)
	//RegexpCveInfluencesDescNew new tpl influences description regexp
	RegexpCveInfluencesDescNew = regexp.MustCompile(`影响性分析说明[:：](?s:(.*?))openEuler评分[:：]`)
	//RegexpCvePrincipleDesc principle description regexp
	RegexpCvePrincipleDesc = regexp.MustCompile(`原理分析[:：](?s:(.*?))openEuler评分[:：]`)
	//RegexpCveOpScore openEuler score regexp
	RegexpCveOpScore = regexp.MustCompile(`openEuler评分[:：](?s:(.*?))受影响版本[:：]`)
	//RegexpCveOpScoreNew new tpl openEuler score regexp
	RegexpCveOpScoreNew = regexp.MustCompile(`openEuler评分[:：](?s:(.*?))受影响版本排查\(受影响/不受影响\)[:：]`)
	//RegexpCveInfluencesVersion influences version regexp
	RegexpCveInfluencesVersion = regexp.MustCompile(`受影响版本[:：](?s:(.*?))规避方案或消减措施[:：]`)
	//RegexpCveInfluencesVersionNew new tpl influences version regexp
	RegexpCveInfluencesVersionNew    = regexp.MustCompile(`受影响版本排查\(受影响/不受影响\)[:：](?s:(.*?))$`)
	RegexpCveInfluencesVersionFixNew = regexp.MustCompile(`受影响版本排查\(受影响/不受影响\)[:：](?s:(.*?))三、漏洞修复`)
	//RegexpCvePlannedMeasures the cve planned measures regexp
	RegexpCvePlannedMeasures = regexp.MustCompile(`规避方案或消减措施[:：](?s:(.*?))受影响的包[:：]`)
	//RegexpCvePlannedMeasures1 the second cve planned measures regexp
	RegexpCvePlannedMeasures1 = regexp.MustCompile(`规避方案或消减措施[:：](?s:(.*?))$`)
	//RegexpCveInfluencesPkg the cve influences package regexp
	RegexpCveInfluencesPkg = regexp.MustCompile(`受影响的包[:：](?s:(.*?))$`)
	//RegexpDigital digital regexp
	RegexpDigital        = regexp.MustCompile(`(\d){1,}(\.\d+)?`)
	RegexpSpecialDigital = regexp.MustCompile(`(cvssv3.[0-9]|cvssv2.[0-9]|CVSSV3.[0-9]|CVSSV2.[0-9]|CVSS[:：]3.[0-9]|CVSS[:：]2.[0-9]|cvss[:：]3.[0-9]|cvss[:：]2.[0-9]|3.[0-9]/|2.[0-9]/|3.[0-9] /|2.[0-9] /)*`) //^((CVSS：3.0|CVSS：2.0|3.0/|2.0/|3.0 /|2.0 /).)*$
	//RegexpSpecialDigital     = regexp.MustCompile(`(cvssv[1-9].[0-9]|CVSSV[1-9].[0-9]|CVSS[:：][1-9].[0-9]|cvss[:：][1-9].[0-9]|[1-9].[0-9]/|[1-9].[0-9] /)*`) //^((CVSS：3.0|CVSS：2.0|3.0/|2.0/|3.0 /|2.0 /).)*$
	RegexpVector             = regexp.MustCompile(`AV:[NLAP](?s:(.*?))/A:[LNH]`)
	RegexpVectorV2           = regexp.MustCompile(`AV:[LAN](?s:(.*))/Au:[MSN](?s:(.*))/A:[NPC]`)
	RegexpScoreTypeV2        = regexp.MustCompile(`(?mi)^CVSS[vV]2.[0-9xX]\s*`) // CVSS V3.0分值:
	RegexpScoreTypeV3        = regexp.MustCompile(`(?mi)^CVSS[vV]3.[0-9xX]\s*`)
	RegexpIsNewTpl           = regexp.MustCompile(`(?mi)^原理分析[：:]\s*`)
	RegexpIsNewTpl2          = regexp.MustCompile(`(?mi)^规避方案或消减措施[:：]\s*`)
	regexpEffectVersion      = regexp.MustCompile(`(?mi)[\d]{1,}\.(.*?)[:：]受影响`)
	regexpNoEffectVersion    = regexp.MustCompile(`(?mi)[\d]{1,}\.(.*?)[:：]不受影响`)
	regexpOtherEffectVersion = regexp.MustCompile(`(?mi)[\d]{1,}\.(.*?)[:：]$`)
	regexpEffectAbiVersion   = regexp.MustCompile(`(?mi)[\d]{1,}\.(.*?)[:：]是`)
	regexpNoEffectAbiVersion = regexp.MustCompile(`(?mi)[\d]{1,}\.(.*?)[:：]否`)
	RegexpIsAbiTpl           = regexp.MustCompile(`(?mi)^(修复)?是否涉及abi变化\(是/否\)[：:]\s*`)
	RegexpIsFixTpl           = regexp.MustCompile(`(?mi)^三、漏洞修复\s*`)
	//RegexpCveAbiVersionNew new tpl influences version regexp
	RegexpCveAbiVersionNew = regexp.MustCompile(`受影响版本排查\(受影响/不受影响\)[:：](?s:(.*?))(修复)?是否涉及abi变化\(是/否\)[:：]`)
	//RegexpCveAbiNew new tpl influences version regexp
	RegexpCveAbiNew    = regexp.MustCompile(`[修复]?是否涉及abi变化\(是/否\)[:：](?s:(.*?))$`)
	RegexpCveAbiFixNew = regexp.MustCompile(`[修复]?是否涉及abi变化\(是/否\)[:：](?s:(.*?))三、漏洞修复`)
	RegMatchCve        = regexp.MustCompile("^CVE-[0-9]+-[0-9]+$")

	Symbol = regexp.MustCompile(">|>=|<|<=|=")
)

var (
	//VectorMapV2 the vector v2.0 map
	VectorMapV2 map[string]map[string]string
	//VectorMap the vector v3.0 map
	VectorMap map[string]map[string]string
	mutex     sync.Mutex
)

//CommentAnalysis issue comment analysis keyword and value container
type CommentAnalysis struct {
	KeyName  string
	KeyIdx   int
	KeyValue string
}

//CaSlice define the CommentAnalysis slice
type CaSlice []CommentAnalysis

func (a CaSlice) Len() int {
	return len(a)
}
func (a CaSlice) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}
func (a CaSlice) Less(i, j int) bool {
	return a[j].KeyIdx > a[i].KeyIdx
}

func init() {
	VectorMap = make(map[string]map[string]string)
	mAv := make(map[string]string)
	mAv["N"] = "Network"
	mAv["L"] = "Local"
	mAv["A"] = "Adjacent"
	mAv["P"] = "Physical"
	VectorMap["AV"] = mAv
	mAc := make(map[string]string)
	mAc["H"] = "High"
	mAc["L"] = "Low"
	VectorMap["AC"] = mAc
	mPr := make(map[string]string)
	mPr["H"] = "High"
	mPr["N"] = "None"
	mPr["L"] = "Low"
	VectorMap["PR"] = mPr
	mUI := make(map[string]string)
	mUI["N"] = "None"
	mUI["R"] = "Required"
	VectorMap["UI"] = mUI
	mS := make(map[string]string)
	mS["U"] = "Unchanged"
	mS["C"] = "Changed"
	VectorMap["S"] = mS
	mC := make(map[string]string)
	mC["H"] = "High"
	mC["L"] = "Low"
	mC["N"] = "None"
	VectorMap["C"] = mC
	mI := make(map[string]string)
	mI["H"] = "High"
	mI["N"] = "None"
	mI["L"] = "Low"
	VectorMap["I"] = mC
	mA := make(map[string]string)
	mA["H"] = "High"
	mA["L"] = "Low"
	mA["N"] = "None"
	VectorMap["A"] = mA
	VectorMapV2 = make(map[string]map[string]string)
	//AV:L/AC:L/Au:N/C:C/I:C/A:C
	mAv2 := make(map[string]string)
	mAv2["L"] = "Local"
	mAv2["A"] = "AdjacentNetwork"
	mAv2["N"] = "Network"
	VectorMapV2["AV"] = mAv2
	mAc2 := make(map[string]string)
	mAc2["H"] = "High"
	mAc2["M"] = "Medium"
	mAc2["L"] = "Low"
	VectorMapV2["AC"] = mAc2
	mAu := make(map[string]string)
	mAu["M"] = "Multiple"
	mAu["S"] = "Single"
	mAu["N"] = "None"
	VectorMapV2["Au"] = mAu
	mCi := make(map[string]string)
	mCi["N"] = "None"
	mCi["P"] = "Partial"
	mCi["C"] = "Complete"
	VectorMapV2["C"] = mCi
	mII := make(map[string]string)
	mII["N"] = "None"
	mII["P"] = "Partial"
	mII["C"] = "Complete"
	VectorMapV2["I"] = mII
	mAi := make(map[string]string)
	mAi["N"] = "None"
	mAi["P"] = "Partial"
	mAi["C"] = "Complete"
	VectorMapV2["A"] = mAi
}

//GenerateCommentAnalysis Generate analytical entities based on comments.
func GenerateCommentAnalysis(content string, organizationID int8) (ca CaSlice) {
	if content == "" {
		return
	}
	commentKeys := []string{}
	if organizationID == 1 {
		commentKeys = EulerCommentKeys
	} else {
		commentKeys = CommentKeys
	}
	for _, v := range commentKeys {
		if strings.Contains(content, v) {
			cm := CommentAnalysis{KeyName: v}
			index := strings.Index(content, v)
			cm.KeyIdx = index
			ca = append(ca, cm)
		}
	}
	//sort ca item by key index
	if len(ca) > 0 {
		//sort by index
		sort.Sort(ca)
		//extract keyword value
		for k := range ca {
			if k == len(ca)-1 {
				start := ca[k].KeyIdx + len(ca[k].KeyName)
				ca[k].KeyValue = content[start:]
			} else {
				start := ca[k].KeyIdx + len(ca[k].KeyName)
				end := ca[k+1].KeyIdx
				ca[k].KeyValue = content[start:end]
			}
		}
	}
	return ca
}

//ParseCommentContent extract comment content based on tags.
func ParseCommentContent(content string, label string) (res string, ok bool) {
	ret := regexp.MustCompile(genCommentRegexpStr(label))
	sm := ret.FindAllStringSubmatch(content, 1)
	if len(sm) > 0 {
		res = sm[0][1]
		ok = true
	}
	return
}

//ParseCommentVector extract vector from issue comment
func ParseCommentVector(content string) string {
	sm := RegexpVector.Find([]byte(content))
	return string(sm)
}

//ExtractVector extract vector from issue body
func ExtractVector(body, scoreType string) string {
	if body == "" {
		return body
	}
	if scoreType == CvsScoreV2 {
		rvs := RegexpVectorV2.Find([]byte(body))
		if rv := string(rvs); rv != "" {
			return rv
		}
	} else {
		rvs := RegexpVector.Find([]byte(body))
		if rv := string(rvs); rv != "" {
			return rv
		}
	}

	return ""
}

//ReadVMValue get vector v3 value from the vector map by keyword
func ReadVMValue(kStr string) (value string) {
	if kStr == "" {
		return ""
	}
	if !strings.Contains(kStr, ":") {
		return ""
	}
	kStr = TrimString(kStr)
	sKs := strings.Split(kStr, ":")
	if len(sKs) != 2 {
		return ""
	}
	mutex.Lock()
	defer mutex.Unlock()
	if _, ok := VectorMap[sKs[0]]; ok {
		value = VectorMap[sKs[0]][sKs[1]]
	}
	return
}

//ReadVMValueV2 get vector v2 value from the vector  map by keyword
func ReadVMValueV2(kStr string) (value string) {
	if kStr == "" {
		return ""
	}
	if !strings.Contains(kStr, ":") {
		return ""
	}
	kStr = TrimString(kStr)
	sKs := strings.Split(kStr, ":")
	if len(sKs) != 2 {
		return ""
	}
	mutex.Lock()
	defer mutex.Unlock()
	if _, ok := VectorMapV2[sKs[0]]; ok {
		value = VectorMapV2[sKs[0]][sKs[1]]
	}
	return
}

//VctToMap Convert vector string value to map
func VctToMap(vct string) (vctMap map[string]string, ok bool) {
	if vct == "" {
		return nil, false
	}
	sp := strings.Split(vct, "/")
	if len(sp) < 1 {
		return nil, false
	}
	vMap := make(map[string]string)
	for _, v := range sp {
		spv := strings.Split(v, ":")
		if len(spv) != 2 {
			continue
		}
		vMap[spv[0]] = v
	}
	if len(vMap) > 0 {
		return vMap, true
	}
	return nil, false

}

//ParseCommentWithAllLabel extract comment value with custom label
func ParseCommentWithAllLabel(content string) map[string]string {
	res := make(map[string]string, 0)
	s, ok := ParseCommentContent(content, IAD)
	if ok {
		res["cve_analysis"] = s
	}
	s, ok = ParseCommentContent(content, PA)
	if ok {
		res["principle_analysis"] = s
	}
	s, ok = ParseCommentContent(content, OES)
	if ok {
		res["openeuler_score"] = TrimString(s)
	}
	s, ok = ParseCommentContent(content, OEV)
	if ok {
		vector := ParseCommentVector(s)
		if vector != "" {
			res["openeuler_vector"] = s
		}
	}
	s, ok = ParseCommentContent(content, IV)
	if ok {
		res["affected_version"] = s
	}
	s, ok = ParseCommentContent(content, CPMM)
	if ok {
		res["solution"] = s
	}
	s, ok = ParseCommentContent(content, IW)
	if ok {
		res["issue_package"] = s
	}

	return res
}

//ExtractCommentAnalysisAllValue Extract all value by issue comment
func ExtractCommentAnalysisAllValue(content string, organizationID int8) map[string]string {
	res := make(map[string]string, 0)
	ca := GenerateCommentAnalysis(content, organizationID)
	if len(ca) > 0 {
		value, ext := ExtractCommentValue(ca, KwAnalysisDesc)
		if ext {
			res["cve_analysis"] = TrimStringNR(value)
		}
		value, ext = ExtractCommentValue(ca, KwEffectVersion)
		if ext {
			value = ExtractCommentEffectVersion(value)
			res["affected_version"] = value
		}
		value, ext = ExtractCommentValue(ca, KwOpenEulerScore)
		if ext {
			score, vector := ExtractCommentOpenEulerScore(value)
			if score != "" {
				res["openeuler_score"] = score
			}
			if vector != "" {
				res["openeuler_vector"] = vector
			}
		}
		if organizationID == 1 {
			value, ext = ExtractCommentValue(ca, KwAbiVersion)
			if ext {
				value = ExtractCommentAbiVersion(value)
				res["abi_version"] = value
			}
		}
	}
	return res
}

//ExtractCommentEffectVersion Extract the affected version from the issue comment
func ExtractCommentEffectVersion(str string) string {
	str = strings.Trim(str, " ")
	str = strings.ReplaceAll(str, " ", "")
	var res []string
	match := regexpEffectVersion.FindAllStringSubmatch(str, -1)
	match2 := regexpNoEffectVersion.FindAllStringSubmatch(str, -1)
	if len(match) > 0 || len(match2) > 0 {
		if len(match) > 0 {
			for _, v := range match {
				if len(v) > 1 {
					tmpV := TrimString(v[1]) + ":受影响"
					tmpV = common.BranchVersionRep(tmpV)
					res = append(res, tmpV)
				}
			}
		}
		if len(match2) > 0 {
			for _, v := range match2 {
				if len(v) > 1 {
					tmpV := TrimString(v[1]) + ":不受影响"
					tmpV = common.BranchVersionRep(tmpV)
					res = append(res, tmpV)
				}
			}
		}
	}
	match3 := regexpOtherEffectVersion.FindAllStringSubmatch(str, -1)
	if len(match3) > 0 {
		for _, v := range match3 {
			if len(v) > 1 {
				tmpV := TrimString(v[1]) + ":"
				tmpV = common.BranchVersionRep(tmpV)
				res = append(res, tmpV)
			}
		}
	}
	if len(res) > 0 {
		return strings.Join(res, ",")
	}
	return ""
}

//ExtractCommentAbiVersion Extract the abi version from the issue comment
func ExtractCommentAbiVersion(str string) string {
	str = strings.Trim(str, " ")
	str = strings.ReplaceAll(str, " ", "")
	var res []string
	match := regexpEffectAbiVersion.FindAllStringSubmatch(str, -1)
	match2 := regexpNoEffectAbiVersion.FindAllStringSubmatch(str, -1)
	if len(match) > 0 || len(match2) > 0 {
		if len(match) > 0 {
			for _, v := range match {
				if len(v) > 1 {
					tmpV := TrimString(v[1]) + ":是"
					tmpV = common.BranchVersionRep(tmpV)
					res = append(res, tmpV)
				}
			}
		}
		if len(match2) > 0 {
			for _, v := range match2 {
				if len(v) > 1 {
					tmpV := TrimString(v[1]) + ":否"
					tmpV = common.BranchVersionRep(tmpV)
					res = append(res, tmpV)
				}
			}
		}
	}
	match3 := regexpOtherEffectVersion.FindAllStringSubmatch(str, -1)
	if len(match3) > 0 {
		for _, v := range match3 {
			if len(v) > 1 {
				tmpV := TrimString(v[1]) + ":"
				tmpV = common.BranchVersionRep(tmpV)
				res = append(res, tmpV)
			}
		}
	}
	if len(res) > 0 {
		return strings.Join(res, ",")
	}
	return ""
}

//ExtractCommentValue  Get the value of CaSlice by keyword
func ExtractCommentValue(ca CaSlice, keyWord string) (string, bool) {
	for _, v := range ca {
		if v.KeyName == keyWord {
			return v.KeyValue, true
		}
	}
	return "", false
}

//ExtractCommentOpenEulerScore Extract openEuler score from issue comment
func ExtractCommentOpenEulerScore(str string) (score, vector string) {
	str = TrimString(str)
	score = ExtractDigital(str)
	vector = ExtractVector(str, CvsScoreV3)
	if vector == "" {
		vector = ExtractVector(str, CvsScoreV2)
	}
	return
}

func genCommentRegexpStr(label string) string {
	return fmt.Sprintf(`\[%s\](?s:(.*?))\[/%s\]`, label, label)
}

//TrimString Remove the \n \r \t spaces in the string
func TrimString(str string) string {
	str = strings.Replace(str, " ", "", -1)
	str = strings.Replace(str, "\n", "", -1)
	str = strings.Replace(str, "\r", "", -1)
	str = strings.Replace(str, "\t", "", -1)
	return str
}

//TrimStringNR Remove the \n \r in the string
func TrimStringNR(str string) string {
	str = strings.Replace(str, "\n", "", -1)
	str = strings.Replace(str, "\r", "", -1)
	str = strings.Replace(str, "\t", "", -1)
	return str
}

//ExtractDigital remove "cvss 3.0" or "cvss 2.0"
func RemoveSpecialDigital(body string) string {
	if body == "" {
		return body
	}
	sds := RegexpSpecialDigital.ReplaceAllString(body, "")
	logs.Info("openEuler_value: ", sds)
	if len(sds) > 0 {
		return sds
	}
	return ""
}

//ExtractDigital Get number in string
func ExtractDigital(body string) string {
	if body == "" {
		return body
	}
	scoreStr := ""
	tempStr := RemoveSpecialDigital(body)
	if tempStr != "" && len(tempStr) > 0 {
		scoreStr = tempStr
	} else {
		scoreStr = body
	}
	sds := RegexpDigital.FindAllStringSubmatch(scoreStr, -1)
	if len(sds) > 0 {
		return sds[0][0]
	}
	return ""
}

//GetCveNumber Extract cveNum from the issue body cveNumber link
func GetCveNumber(ov string) string {
	if v := regexpCveNumberLink.Find([]byte(ov)); len(v) > 0 {
		sv := string(v)
		start := strings.Index(sv, "[") + 1
		end := strings.Index(sv, "]")
		return sv[start:end]
	}
	return ov
}

//GetCveNumber Extract cve package from the issue body whe package is a link
func GetCvePkg(str string) string {
	origin := TrimString(str)

	return ParseAnchor(origin)
}

// ParseAnchor extract anchors from a link representation of the form [anchor](url)
func ParseAnchor(origin string) string {
	reg := regexp.MustCompile(`\[(.*?)]\((.*?)\)`)
	v := reg.Find([]byte(origin))
	if len(v) == 0 {
		return origin
	}

	sv := string(v)
	start := strings.Index(sv, "[")
	end := strings.Index(sv, "](")

	return sv[start+1 : end]
}
